/*
 * Copyright 2011 Raimon Rodriguez
 *
 * This file is part of sgfx.
 *
 * sgfx is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * sgfx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with sgfx. If not, see <http://www.gnu.org/licenses/>.
 */

package com.googlecode.sgfx.base;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ListModel;
import javax.swing.event.ListDataListener;

/**
 * Contains utility methods related to {@code ListModel}s, similar to the {@code Collections} class.
 * 
 * @author raimon.rodriguez
 */
public final class ListModels {
    private static final ListModel EMPTY_LIST_MODEL = new EmptyListModel();


    private ListModels() {}


    /**
     * Creates a mutable list instance containing the elements of the specified 
     * {@code ListModel} instance. Therefore, changes to the returned list won't affect the model
     * and vice versa. After the operation, the index of each element in the new list will be
     * identical to its index in the list model.
     * 
     * @param pModel the specified list model.
     * @return a new list containing all the elements in the list model with the same index.
     * @throws NullPointerException if <code>pModel</code> is null.
     */
    public static <E> List<E> newList(ListModel pModel) {
        Preconditions.checkNotNull(pModel);

        List<E> tResult = new ArrayList<E>(pModel.getSize());
        copyElements(pModel, tResult);

        return tResult;
    }

    /**
     * Creates a mutable list instance containing the elements of the specified list model. Changes
     * to the list don't affect the model. The order will be the same index order.
     *
     * Enforces the type specified. If an element in the model if not of the specified type, an
     * exception is thrown.
     *
     * @param pModel the specified list model.
     * @return a new list containing all the elements in the list model with the same index.
     * @throws NullPointerException if <code>pModel</code> or <code>pType</code> is null.
     * @throws ClassCastException if one element is found to be of a type different than the
     * specified.
     */
    static <E> List<E> newList(ListModel pModel, Class<E> pType) {
        Preconditions.checkNotNull(pModel, "pModel");
        Preconditions.checkNotNull(pType, "pType");

        List<E> tResult = Collections.checkedList(new ArrayList<E>(pModel.getSize()), pType);
        copyElements(pModel, tResult);

        return tResult;
    }

    /**
     * Creates a mutable event list instance containing the elements of the specified 
     * {@code ListModel} instance. Therefore, changes to the returned list won't affect the model
     * and vice versa. After the operation, the index of each element in the new event list will be
     * identical to its index in the list model.
     *
     * @param pModel the specified list model.
     * @return a new event list containing all the elements in the list model with the same index.
     * @throws NullPointerException if <code>pModel</code> is null.
     */
    public static <E> EventList<E> newEventList(ListModel pModel) {
        Preconditions.checkNotNull(pModel, "list model is null");
        
        EventList<E> tResult = new BasicEventList<E>(pModel.getSize());
        copyElements(pModel, tResult);
        
        return tResult;
    }

    /**
     * Returns a list model backed by the specified list.
     * 
     * @param pBackingList
     * @return
     */
    public static <E> ListModel asListModel(List<? extends E> pBackingList) {
        throw new UnsupportedOperationException("method not yet implemented");
    }

    /**
     * <p>
     * Returns the empty immutable list model. Never returns null. The returned list model does not
     * keep a list of registered list data listeners to it so attempting to add or remove listeners
     * are no-ops.
     * </p>
     *
     * <p>
     * This list model implementation is preferable over an empty DefaultListModel created by default
     * when the no-arg JList constructor is called as it has a smaller memory footprint
     * (DefaultListModel instantiates an empty vector and an EventListenerList right out of the box).
     * So, instead of creating a new JList like this: {@code new JList()}, consider using:
     * {@code new JList(ListModels.emptyListModel())}
     * </p>
     *
     * @return the empty immutable list model that keeps no record of registered listeners to it.
     */
    public static ListModel emptyListModel() {
        return EMPTY_LIST_MODEL;
    }


    @SuppressWarnings("unchecked")
    private static <E> void copyElements(ListModel pSource, List<E> pDestination) {
        for (int i = 0; i < pSource.getSize(); i++) {
            pDestination.add((E) pSource.getElementAt(i));
        }
    }


    private static class EmptyListModel implements ListModel {

        public int getSize() {
            return 0;
        }

        public Object getElementAt(int pIndex) {
            throw new IndexOutOfBoundsException(String.format("index=%s, size=%s", pIndex, getSize()));
        }

        public void addListDataListener(ListDataListener pListener) {}

        public void removeListDataListener(ListDataListener pListener) {}
    }
}
